﻿/*
* This file is part of WinMergeFS.
*
* WinMergeFS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.

* WinMergeFS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WinMergeFS.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Dokan;
using WinMergeFS.Common;
using RealtimeTrace;

namespace WinMergeFS.API
{
    /// <summary>
    /// Manages mounting threads.
    /// </summary>
    internal class ThreadManager : Itraceable
    {
        #region RealtimeTrace

        /// <summary>
        /// Use this method to initialize the RealTimeTrace.
        /// This method should be called within the class constructor(s) 
        /// if you want to override the default RealtimeTrace configuration.
        /// </summary>
        private void InitializeRealTimeTrace()
        {
            this.RealTimeTraceProvider = DataProviderType.LogFile;
            this.PreferredDumpType = DumpType.XML;
        }

        private DataProviderType _RealTimeTraceProvider = DataProviderType.LogFile;
        /// <summary>
        /// Gets or Sets the Data Store provider for the RealTimeTrace.
        /// The default Data Storage provider is a raw text file.
        /// For Windows application, this raw text file is stored at the same level of the running executable.
        /// For ASP.NET application, this raw text file is stored at the same level of the aspx page.
        /// For UI Components, this raw text file is stored in the "My Documents" user folder.
        /// By default the name of the log file is *RealtimeTrace.log*
        /// </summary>
        public DataProviderType RealTimeTraceProvider
        {
            get
            {
                return this._RealTimeTraceProvider;
            }
            set
            {
                this._RealTimeTraceProvider = value;
            }
        }

        private DumpType _PreferredDumpType = DumpType.XML;
        /// <summary>
        /// Gets or Sets the type of dump for the current class.
        /// </summary>
        public DumpType PreferredDumpType
        {
            get
            {
                return this._PreferredDumpType;
            }
            set
            {
                this._PreferredDumpType = value;
            }
        }

        private bool _HasErrors = false;
        /// <summary>
        /// Checks if an error has occured within this class instance.
        /// The Itraceable implementer should only read this property.
        /// This property is set only by the RealtimeTrace layer.
        /// </summary>
        public bool HasErrors
        {
            get { return this._HasErrors; }
            set { this._HasErrors = value; }
        }

        private System.String _LastError = System.String.Empty;
        /// <summary>
        /// Gets the last known error from the last call of a public/private member within this class instance.
        /// The Itraceable implementer should only read this property.
        /// This property is set only by the RealtimeTrace layer.
        /// </summary>
        public string LastError
        {
            get { return this._LastError; }
            set
            {
                if (value == null)
                {
                    value = System.String.Empty;
                }
                this._LastError = value;
            }
        }

        private System.String _Errors = System.String.Empty;
        /// <summary>
        /// Gets all errors from the last call of a public member within this class instance.
        /// The Itraceable implementer should only read this property.
        /// This property is set only by the RealtimeTrace layer.
        /// </summary>
        public string Errors
        {
            get { return this._Errors; }
            set
            {
                if (value == null)
                {
                    value = System.String.Empty;
                }
                this._Errors = value;
            }
        }

        #endregion

        private const int DokanThreadCount = 20;

        private static System.Collections.Generic.Dictionary<char, MountEntry> _mounts = new Dictionary<char, MountEntry>();

        private LoggerEventHandler _logger;

        /// <summary>
        /// Contructor
        /// </summary>
        /// <param name="logger">LoggerHandler</param>
        public ThreadManager(LoggerEventHandler logger)
        {
            try
            { _logger = logger; }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex); throw; }
        }

        /// <summary>
        /// Returns a dictionary of running threads.
        /// </summary>
        public System.Collections.Generic.Dictionary<char, Profile> MountedProfiles
        {
            get
            {
                try
                {
                    Dictionary<char, Profile> _newdict = new Dictionary<char, Profile>();
                    foreach (MountEntry mount in _mounts.Values)
                    {
                        _newdict.Add(mount.Profile.DriveLetter, mount.Profile);
                    }
                    return _newdict;
                }
                catch (Exception ex)
                { TraceFactory.InsertTrace(this, ex); return null; }
            }
            set
            {
            }
        }

        /// <summary>
        /// Specifies the profile to be mounted.
        /// </summary>
        /// <param name="profile">Specifies the profile to be mounted.</param>
        public void MountProfile(Profile profile)
        {
            try
            {
                if (!_mounts.ContainsKey(profile.DriveLetter))
                {
                    _logger("ThreadManager: Mounting device " + profile.DriveLetter, LogLevel.debug);
                    Thread _thread = new Thread(this.ThreadMethode);
                    _mounts.Add(profile.DriveLetter, MountEntry.NewEntry(_thread, profile));
                    _thread.Start(profile);
                }
                else
                { throw new ApplicationException("Driveletter already used"); }
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, profile); }
        }

        /// <summary>
        /// Unmounts the profile and exits threads.
        /// </summary>
        public void UnmountProfile(Profile profile)
        {
            try
            { DokanNet.DokanUnmount(profile.DriveLetter); }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, profile); }
        }

        /// <summary>
        /// Unmount all profiles at once.
        /// </summary>
        public void UnmountAll()
        {
            try
            {
                foreach (MountEntry mount in _mounts.Values)
                {
                    UnmountProfile(mount.Profile);
                }
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex); }
        }

        /// <summary>
        /// Mounts a specified profile in exclusive mode.
        /// </summary>
        /// <param name="profile">Specifies the profile to be mounted.</param>
        public void MountExclusive(Profile profile)
        {
            try
            {
                DokanOptions opt = new DokanOptions();
                opt.DebugMode = true;
                opt.VolumeLabel = profile.VolumeID;
                opt.DriveLetter = profile.DriveLetter;
                opt.ThreadCount = 1; // DokanThreadCount;
                DokanNet.DokanMain(opt, new FSOperations(profile, _logger));
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, profile); }
        }

        /// <summary>
        /// The method running as thread.
        /// </summary>
        /// <param name="profile">Specifies the profile for the thread.</param>
        private void ThreadMethode(Object profile)
        {
            try
            {
                Profile _profile = (Profile)profile;
                DokanOptions opt = new DokanOptions();
                opt.DebugMode = false;
                opt.VolumeLabel = _profile.VolumeID;
                opt.DriveLetter = _profile.DriveLetter;
                opt.ThreadCount = DokanThreadCount;
                _logger("Thread: Mounting device: " + _profile.DriveLetter, LogLevel.debug);
                DokanNet.DokanMain(opt, new FSOperations(_profile, _logger)); //Thread stops here while drive mounted.

                //Drive was unmounted
                _mounts.Remove(_profile.DriveLetter);
                _logger("Thread: Device unmounted: " + _profile.DriveLetter, LogLevel.debug);
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, profile); }
        }
    }
}
